Popularmente conocida como la “Ley Trans”, es uno de los proyectos más controvertidos del gobierno de coalición de izquierda. Incluso dentro de la coalición hay opiniones opuestas. Si se aprueba el proyecto de ley, España se convertiría en el país más grande de Europa en permitir que las personas cambien legalmente el nombre y género en sus documentos de identidad sin la necesidad de años de terapia hormonal o diagnóstico médico. Esto ha provocado un gran debate y/o fuertes posicionamientos al respecto, lo que también se ha reflejado en redes sociales como Twitter.
En este documento se van a analizar 14.1 millones de tweets del 13/01/2021 al 12/10/2022 en relación a la https://www.boe.es/buscar/doc.php?id=BOE-A-2023-5366. El dataset contiene más de 14 millones de tweets, con 257.000 usuarios y 738.651 aristas (respuestas entre usuarios).
# Importaciones de las librerias que utilizaremos
library(quanteda)
## Package version: 4.0.2
## Unicode version: 15.1
## ICU version: 74.2
## Parallel computing: 16 of 16 threads used.
## See https://quanteda.io for tutorials and examples.
library(quanteda.textplots)
library(stringr)
library(wordcloud2)
library(tm)
## Loading required package: NLP
##
## Attaching package: 'NLP'
## The following objects are masked from 'package:quanteda':
##
## meta, meta<-
##
## Attaching package: 'tm'
## The following object is masked from 'package:quanteda':
##
## stopwords
library(tidytext)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ purrr 1.0.2
## ✔ forcats 1.0.0 ✔ readr 2.1.5
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ purrr::%||%() masks base::%||%()
## ✖ ggplot2::annotate() masks NLP::annotate()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(syuzhet)
library(dplyr)
library(textdata)
library(ggplot2)
library(htmlwidgets)
library(igraph)
##
## Attaching package: 'igraph'
##
## The following objects are masked from 'package:lubridate':
##
## %--%, union
##
## The following objects are masked from 'package:dplyr':
##
## as_data_frame, groups, union
##
## The following objects are masked from 'package:purrr':
##
## compose, simplify
##
## The following object is masked from 'package:tidyr':
##
## crossing
##
## The following object is masked from 'package:tibble':
##
## as_data_frame
##
## The following object is masked from 'package:quanteda.textplots':
##
## as.igraph
##
## The following objects are masked from 'package:stats':
##
## decompose, spectrum
##
## The following object is masked from 'package:base':
##
## union
# Establecer directorio de trabajo
setwd("/home/antonio/Repos/ley-trans")
El dataset es de Kaggle y se puede encontrar en el siguiente enlace: https://www.kaggle.com/datasets/hectorfr1984/spanish-trans-law-twitter-dataset
df <- data.frame()
dataset_csv <- read.csv("spanish-trans-law-twitter-dataset.csv", header = TRUE, sep = ",", fill = TRUE)
## Warning in scan(file = file, what = what, sep = sep, quote = quote, dec = dec,
## : embedded nul(s) found in input
df <- rbind(dataset_csv, df)
Guardamos el dataset en un archivo rda para poder trabajar con él más adelante.
# Guardamos el data frame en un archivo rda
save(df, file = "df.rda")
El dataset no está normalizado, ya que en cada fila tenemos información de los usuarios la cual debería estar en una tabla aparte, por lo tanto vamos a separar el dataframe en dos dataframes de usuarios y tweets.
El dataframe de usuarios tendrá los siguientes campos:
user_id, user_screen_name,
user_description, user_created_at_iso,
user_followers y user_friends.
df_usuarios <- df %>%
select(user_id, user_screen_name, user_description, user_created_at_iso, user_followers, user_friends) %>%
distinct()
Los followers y friends van variando en función del tiempo, por lo tanto para no tener filas repetidas, usaremos el máximo de followers y friends de cada usuario.
df_usuarios <- df_usuarios %>%
group_by(user_id) %>%
summarise(user_screen_name = first(user_screen_name),
user_description = first(user_description),
user_created_at_iso = first(user_created_at_iso),
user_followers = max(user_followers),
user_friends = max(user_friends))
También normalizaremos la fecha de creación de los usuarios.
df_usuarios$user_created_at_iso <- as.POSIXct(df_usuarios$user_created_at_iso, format="%Y-%m-%d")
Guardamos el dataframe de usuarios en un archivo rda.
# Guardamos el data frame en un archivo rda
save(df_usuarios, file = "df_usuarios.rda")
El dataframe de usuarios tendrá los siguientes campos:
tweet_id, in_reply_to_user_id,
user_id, retweet_id,
retweet_user_id, tweet_created_at_iso,
tweet_full_text.
df_tweets <- df %>%
select(tweet_id, in_reply_to_user_id, user_id, retweet_id, retweet_user_id, tweet_created_at_iso, tweet_full_text) %>%
distinct()
Guardamos el dataframe de tweets en un archivo rda.
# Guardamos el data frame en un archivo rda
save(df_tweets, file = "df_tweets.rda")
Crearemos un nuevo dataframe df_filtered con el
contenido de los tweets filtrado.
df_filtered <- df_tweets
Para poder trabajar con las fechas, vamos a convertirlas a formato POSIXct
df_filtered$tweet_created_at_iso <- as.POSIXct(df_filtered$tweet_created_at_iso, format="%Y-%m-%d")
Es necesario pasar los tweets a minúsculas para evitar que palabras iguales pero escritas de forma diferente se consideren diferentes.
#Convertimos todas las letras a minúsculas
df_filtered$tweet_full_text <- tolower(df_filtered$tweet_full_text)
Las stopwords son palabras que no aportan significado al texto, como artículos, preposiciones, etc. Vamos a eliminarlas de los tweets.
# Eliminar las stopwords del español
df_filtered$tweet_full_text <- removeWords(df_filtered$tweet_full_text, stopwords("es"))
Se ha observado que es muy popular en los tweets el uso de stopwords acortadas, como “q” en lugar de “que”, “x” en lugar de “por”, etc. Por lo tanto, eliminaremos las palabras de una sola letra.
# Eliminar las palabras de una sola letra
df_filtered$tweet_full_text <- str_replace_all(df_filtered$tweet_full_text, "\\b[a-zA-Z]\\b", "")
Eliminamos las URLs de los tweets.
# Eliminar urls
df_filtered$tweet_full_text <- str_replace_all(df_filtered$tweet_full_text, "https?://([^/\\s]++)\\S*+|http?://([^/\\s]++)\\S*+", "")
Eliminamos los números y los caracteres especiales.
# Eliminar los números y los carácteres especiales
df_filtered$tweet_full_text <- str_replace_all(df_filtered$tweet_full_text, "[^[:alpha:][:space:]]", "")
Por último eliminamos las palabras que no sean de nuestro interés. Las palabras “ley” y “trans” son redundantes ya que todos los tweets van en relación a ese mismo tema. La palabra “si” es muy común en los tweets y no aporta información relevante, y “rt” es la abreviatura de “retweet”.
# Eliminar palabras que no sean de nuestro interés
palabras_eliminar = c("ley", "trans", "leytrans", "rt", "si")
df_filtered$tweet_full_text <- removeWords(df_filtered$tweet_full_text, palabras_eliminar)
# Guardamos el data frame en un archivo r### 4. Frecuencia de palabras <a name="frecuencia"></a>da
save(df_filtered, file = "df_filtered.rda")
Creamos el corpus de los tweets, que como ya se ha mencionado, es un conjunto de documentos de texto.
# Crear un corpus con los tweets
corpus_tweets <- corpus(df_filtered$tweet_full_text)
Creamos los tokens, que son las palabras que componen los documentoss. Indicamos que queremos eliminar los números, los signos de puntuación, los símbolos, los separadores y las URLs.
# Generamos los tokens
token_tweets <-quanteda::tokens(corpus_tweets,
what = "word",
remove_numbers = TRUE,
remove_punct = TRUE,
remove_symbols = TRUE,
remove_separators = TRUE,
remove_url = TRUE)
# Generamos la matriz de frecuencia de los tweets
matrix_tweets <-dfm(token_tweets)
Primero sacamos las 10 palabras más frecuentes con la ayuda de nuestra matriz de frecuencia.
# Mostramos las 10 palabras más frecuentes
top_10 <- topfeatures(matrix_tweets, 10)
top_10
## personas mujeres derechos psoe hoy género
## 148102 107116 96472 95393 93510 90521
## irenemontero años lgtbi ser
## 71705 65588 65289 64994
Pasamos el objeto top_10 a un data frame para poder
hacer el gráfico.
# Convertimos el objeto top_10 a un data frame
top_10_df <- data.frame(palabra = names(top_10), frecuencia = as.numeric(top_10))
Creamos el gráfico de barras con las 10 palabras más frecuentes.
# Barplot
barplt <- ggplot(top_10_df, aes(x = reorder(palabra, frecuencia), y = frecuencia)) +
geom_bar(stat = "identity", fill = "steelblue") +
coord_flip() +
theme_minimal() +
labs(
x = "Palabra",
y = "Frecuencia")
barplt
Por último, guardamos el gráfico de barras.
# Guardamos el gráfico
ggsave("barplot.png", plot = barplt, width = 10, height = 5, units = "in")
En este caso sacaremos las 100 palabras más frecuentes y las pasamos a data frame.
# Hacemos el dataframe del top 100
top_100 <- topfeatures(matrix_tweets, 100)
top_100_df <- data.frame(palabra = names(top_100), frecuencia = as.numeric(top_100))
Creamos el wordcloud con las 100 palabras más frecuentes. Ajustamos
los parametros minRotation y maxRotation y
rotateRatio para darle un aspecto más atractivo.
# Wordcloud
worldcloud_palabras <- wordcloud2(top_100_df, size = 1.0 , minRotation = -pi/6, maxRotation = -pi/6, rotateRatio = 1)
worldcloud_palabras
En el wordcloud la palabra más predominante es personas, ya que se usa mucho “personas trans”; lo mismo pasa con mujeres (“mujeres trans”). También podemos encontrar los 4 pártidos políticos más importantes de España: PSOE, PP, Vox y Podemos. Destaca también la palabra “menores” que va en relación a las críticas de la ley por parte de los detractores. Otras palabras en relación al tema son género, sexo y lgtpi.
Guardamos el wordcloud. Solo se guardará el html, ya que el wordcloud2 no se puede guardar en formato png.
# Guardar el wordcloud
htmlwidgets::saveWidget(worldcloud_palabras, "wordcloud_palabras.html", selfcontained = TRUE)
Generamos los bigramas.
# Generamos los bigramas
bigramas <- tokens_ngrams(token_tweets, n = 2)
Creamos la matriz de frecuencia de los bigramas.
# Generamos la matriz de frecuencia de los bigramas
matrix_bigramas <- dfm(bigramas)
Sacamos las 100 palabras más frecuentes de los bigramas.
top_100 <- topfeatures(matrix_bigramas, 100)
top_100_df <- data.frame(palabra = names(top_100), frecuencia = as.numeric(top_100))
Creamos el wordcloud de los bigramas.
# Wordcloud
worldcloud_palabras_bigramas <- wordcloud2(top_100_df, size = 1.0 , minRotation = -pi/6, maxRotation = -pi/6, rotateRatio = 1)
En el
caso de los bigramas figuras políticas como Irene Montero (ex-ministra
de igualdad) y Carmen Calvo (actual presidenta del Consejo de Estado).
Otras palabras en relación al tema son “identidad genero”,
“autodeterminación genero” y “derechos personas”. En general aparecen
muchos bigramas con la palabra derechos, lo cual puede indicar que la
ley se ve como un avance en los derechos de las personas.
Lo guardamos.
# Guardar el wordcloud
htmlwidgets::saveWidget(worldcloud_palabras_bigramas, "wordcloud_bigramas.html", selfcontained = TRUE)
El análisis de sentimientos es una técnica que consiste en determinar
si un texto es positivo, negativo o neutro. Para ello, se asigna un
valor numérico a cada palabra del texto mediante un diccionario
sentimental. En nuestro caso, al ser un dataset en español, no existen
muchos diccionarios sentimentales, por lo que utilizaremos la función
get_sentiment del paquete syuzhet, la cual
primero traduce la palabra al inglés y luego le asigna un valor
sentimental.
Primero creamos un tidyset con los tweets.
# Creamos un tidyset con los tweets
tidy <- df_filtered %>%
unnest_tokens(word, tweet_full_text)
Para ahorrarnos tiempo de computo, vamos a generar un diccionario sentimental con todas las palabras del dataset.
# Creamos un diccionario sentimental con todas las palabras del dataset usando la función get_sentiment(word, method = "nrc", lang="spanish")
sentiment_dict <- tidy %>%
distinct(word) %>%
mutate(sentiment = get_sentiment(word, method = "nrc", lang="spanish"))
Agregaremos algunas palabras customizadas que el diccionario sentimental no ha detectado.
palabras_añadir = c("facha","nazi","racista",
"racistas","nazis","gilipollas",
"sanchista","sanchistas","maricones",
"alertafeminista","fachas","vertederofacha",
"neonazis","neonazi","feminazis",
"terfisfeminazi","transfobo","machista",
"machismo","sexta","transfobia",
"terf","terfs","terfas",
"retrograda","transexcluyente","transexcluyentes",
"feminazi","fachasnazis","maricas",
"machirulo","perro","derrogación",
"dictador","franco","fascista",
"progre","retroceso","podemita",
"podemitas","retrogrado","homófobo",
"homófobos","homófobas","homofobia",
"rojo", "rojos","puto",
"putos", "bulo","bulos",
"comunista","comunistas","violador",
"violadores","patriarcado","patriarcal",
"maricón","comunismo","socialcomunista",
"pitorreo","terfachas","travelo")
# Si la palabra ya está recogida, cambiamos su valor sentimental a -3
new_sentiment_dict <- sentiment_dict %>%
mutate(sentiment = ifelse(word %in% palabras_añadir, -3, sentiment))
# Si la palabra no está recogida, la añadimos con valor sentimental -3
new_sentiment_dict <- new_sentiment_dict %>%
anti_join(data.frame(word = palabras_añadir), by = "word") %>%
bind_rows(data.frame(word = palabras_añadir, sentiment = -3))
Una vez tenemos el diccionario sentimental, calculamos el sentimiento de cada tweet.
# Ahora a cada tweet le añadimos el sentimiento
tweets_sentiment <- tidy %>%
inner_join(new_sentiment_dict, by = "word") %>%
group_by(tweet_id) %>%
summarise(sentiment = sum(sentiment))
Mostramos el histograma de los sentimientos.
ggplot(data = tweets_sentiment, aes(x = sentiment)) +
geom_bar(color = 'darkslategray', fill = 'steelblue') +
xlab("Sentimiento") +
ylab("Cantidad de Tweets") +
ggtitle("Gráfico sentimiento")
# Guardamos el gráfico
ggsave("sentiment.png", plot = last_plot(), width = 10, height = 5, units = "in")
Por último, calculamos el porcentaje de tweets positivos, negativos y neutros.
# Calcular porcentaje de tweets positivos, negativos y neutros
tweets_sentiment %>%
summarise(
positivos = sum(sentiment > 0) / n() * 100,
negativos = sum(sentiment < 0) / n() * 100,
neutros = sum(sentiment == 0) / n() * 100
)
## # A tibble: 1 × 3
## positivos negativos neutros
## <dbl> <dbl> <dbl>
## 1 34.9 29.1 36.0
Vemos que en general los tres tipos de sentimientos están bastante equilibrados, aunque predominan los neutros y entre los positivos y negativos ganan los positivos. Que haya más sentimiento positivo en relación a la ley puede indicar que en general hay mas apoyo que detractores de la ley.
Generaremos el gráfico del sentimiento en el tiempo con ambos diccionarios para comprobar si las palabras añadidas han afectado al sentimiento de los tweets.
Primero, creamos un dataframe con la fecha y el sentimiento de cada tweet.
# Creamos un dataframe con la fecha y el sentimiento de cada tweet
df_sentimiento <- df_filtered %>%
inner_join(tweets_sentiment, by = "tweet_id") %>%
select(tweet_created_at_iso, sentiment)
Creamos un gráfico de sentimiento en el tiempo.
# Gráfico de sentimiento en el tiempo
ggplot(data = df_sentimiento,
aes(x = tweet_created_at_iso,
y = sentiment
)) +
geom_smooth(color = 'darkslategray') +
xlab("Fecha") +
ylab("Sentimiento") +
scale_x_datetime(date_labels = "%b", date_breaks = "1 month") +
ggtitle("Gráfico de sentimiento en el tiempo")
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
El 10 de marzo de 2021 comenzo la huelga de hambre de Mar Cambrolle la cual ya ha sido mencionada en el análisis de hashtags. A partir de esa fecha, el sentimiento positivo a ido creciendo. El 17 de marzo de ese mismo año se registró la Proposición de Ley para la igualdad real y efectiva de las personas trans en el Congreso de los Diputados, lo que puede haber influido en el sentimiento positivo. El 18 de mayo se votó por la admisión de la ley, la cual fue rechazada debido a los votos de PP, Vox y por la abstención del PSOE.
El 29 de junio de 2021, el ministerio de igualdad presentó el anteproyecto de ley, en el que se incluía la autodeterminación de género para mayores de 16 años, y la eliminación de la necesidad de diagnóstico médico para cambiar el género en el DNI. A partir de la presentación del anteproyecto, se observa un mínimo que por primera vez es negativo en el gráfico. Puede deverse a que cuando ses presentó el anteproyecto, se incluyeron medidas que no gustaron a los detractores de la ley.
En junio de 2022 se observa un gran pico de positividad, se puede deber a que el 27 de junio de 2022 el documento de anteproyecto fue aprobado, pasando a ser un proyecto de ley. Luego, vemos un gran descenso en la positividad, que puede deberse a la perdida del gran interés inicial de la izquierda y a las críticas de los retractores.
El 8 de septiembre se anunció que se reducirían la mitad de los plazos de la ley, lo cual haría que se aprobase ante la ley. Justo en este periodo comienza a subir la positividad, lo cual puede deberse a que la ley finalmente se aprobará.
Si lo comparemos con el gráfico generado usando el diccionario sin modificar, vemos que las palabras customizadas han afectado notablemente al sentimiento de los tweets.
tweets_sentiment_old <- tidy %>%
inner_join(sentiment_dict, by = "word") %>%
group_by(tweet_id) %>%
summarise(sentiment = sum(sentiment))
df_sentimiento_old <- df_filtered %>%
inner_join(tweets_sentiment_old, by = "tweet_id") %>%
select(tweet_created_at_iso, sentiment)
ggplot(data = df_sentimiento_old,
aes(x = tweet_created_at_iso,
y = sentiment
)) +
geom_smooth(color = 'darkslategray') +
xlab("Fecha") +
ylab("Sentimiento") +
scale_x_datetime(date_labels = "%b", date_breaks = "1 month") +
ggtitle("Gráfico de sentimiento en el tiempo")
## `geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
Nuestro dataset no contiene la id de los tweets de respuesta, pero si la de los usuarios a los que se responde. Por lo tanto, crearemos un grafo de interacciones entre usuarios.
Primero, creamos un data frame con las columnas user_id
y in_reply_to_user_id.
df_interacciones <- df_filtered %>%
select(user_id, in_reply_to_user_id) %>%
na.omit()
Creamos el grafo de interacciones.
grafo <- graph_from_data_frame(df_interacciones, directed = TRUE)
Crearemos una función para ver los usuarios con más interacciones.
# Sacamos el usuario con más interaccionesmarx
usuarios_interacciones <- degree(grafo, mode = "in")
# Creamos un data frame con los usuarios y sus interacciones
df_usuarios_interacciones_id <- data.frame(
user_id = as.numeric(names(usuarios_interacciones)),
interacciones = as.numeric(usuarios_interacciones))
df_usuarios_interacciones_id <- df_usuarios_interacciones_id[order(-df_usuarios_interacciones_id$interacciones),]
# Al dataframe le añadimos una columna con el nombre de usuario
df_usuarios_interacciones <- df_usuarios_interacciones_id %>%
inner_join(df_usuarios, by = "user_id")
# Seleccionamos solo las columnas del nombre y las interacciones
df_usuarios_interacciones <- df_usuarios_interacciones %>%
select(user_screen_name, interacciones)
head(df_usuarios_interacciones, 10)
## user_screen_name interacciones
## 1 IreneMontero 2577
## 2 PSOE 846
## 3 sanchezcastejon 727
## 4 PODEMOS 582
## 5 carmencalvo_ 544
## 6 LaEtxebarria 478
## 7 PabloEchenique 462
## 8 ierrejon 433
## 9 JMLm555 333
## 10 CarlaAntonelli 333
Vemos que los 5 usuarios más relevantes son políticos de izquierdas, lo que puede indicar que la izquierda habla más del tema que la derecha.
Creamos un data frame con los usuarios y sus seguidores.
df_usuarios_seguidores <- df_usuarios %>%
select(user_id, user_screen_name, user_followers) %>%
arrange(desc(user_followers))
df_usuarios_seguidores
## # A tibble: 289,380 × 3
## user_id user_screen_name user_followers
## <dbl> <chr> <int>
## 1 33884545 CNNEE 21850774
## 2 7996082 el_pais 8686360
## 3 236636515 lopezdoriga 8043969
## 4 9633802 ELTIEMPO 7548040
## 5 124172948 la_patilla 7454855
## 6 16676396 El_Universal_Mx 6809985
## 7 107120856 brozoxmiswebs 6651021
## 8 14834302 elespectador 6494442
## 9 25992212 todonoticias 5505632
## 10 15095537 marca 5427385
## # ℹ 289,370 more rows
Mucho de las cuentas con más seguidores son cuentas de medios de comunicación. Aunque seán los usuarios con más seguidores, vemos que no son los usuarios con más interacciones.
Debido al tamaño y la complejidad del dataset, hemos decidido centrarnos en comunidades pequeñas pero de las cuales tenemos la certeza de que están a favor o en contra de la ley.
Al intentar analizar las comunidades mediante un algoritmo como el de Louvain, los resultados no son los esperados. Por lo tanto, utilizaremos un método más simple, que consiste en agrupar los usuarios por el contenido de su biografía.
Observando a los usuarios, vimos que es bastante común poner banderas en la descripción de los perfiles. Estas banderas, aunque no siempre tienen que ver con la opinión sobre la ley, nos sirven como punto de partida para agrupar a los usuarios.
Primero, sacamos los usuarios que tienen en su descripción la bandera trans. Hemos asumido que los usuarios que tienen la bandera trans en su descripción están a favor de la ley.
Obtenemos una comunidad de 722 usuarios.
df_usuarios_favor <- df_usuarios %>%
filter(str_detect(user_description, "🏳️⚧️"))
df_usuarios_favor
## # A tibble: 722 × 6
## user_id user_screen_name user_description user_created_at_iso user_followers
## <dbl> <chr> <chr> <dttm> <int>
## 1 13607 hober "she/her. 🏳️⚧️🏳️🌈 @W… 2006-11-21 00:00:00 3521
## 2 10321722 sofzapatazavala "She/Her ✸ Muje… 2007-11-17 00:00:00 1895
## 3 15451808 inaxo "Altzatarra. Ku… 2008-07-16 00:00:00 504
## 4 16941652 nacho_w "He/Him 🏳️🌈🏳️⚧️" 2008-10-24 00:00:00 1489
## 5 21322175 MTheLeon "🏳️⚧️ (She/Her/Ell… 2009-02-19 00:00:00 1662
## 6 21427450 DANCAMPERO "Comunicóloga o… 2009-02-20 00:00:00 899
## 7 25220784 CassieGemini "Persona no bin… 2009-03-19 00:00:00 277
## 8 28086825 danimrlt "Programmer of … 2009-04-01 00:00:00 53
## 9 31217555 valandres__ "Destino clande… 2009-04-14 00:00:00 570
## 10 33061864 caniculee "Irish🇮🇪 in Mad… 2009-04-19 00:00:00 842
## # ℹ 712 more rows
## # ℹ 1 more variable: user_friends <int>
El siguiente paso será recoger todos los tweets de los usuarios a favor de la ley.
df_tweets_favor <- df_filtered %>%
inner_join(df_usuarios_favor, by = "user_id")
Sacaremos los usuarios que tengan en su descripción la palabra “vox”.
En este caso tenemos una comunidad de 369 usuarios, aproximadamente la mitad que la comunidad a favor (esto no significa que haya, en general, la mitad de usuarios en contra que a favor).
df_usuarios_vox <- df_usuarios %>%
filter(str_detect(user_description, "vox"))
df_usuarios_vox
## # A tibble: 369 × 6
## user_id user_screen_name user_description user_created_at_iso user_followers
## <dbl> <chr> <chr> <dttm> <int>
## 1 4.90e7 ivanedlm "Portavoz en el… 2009-06-20 00:00:00 324015
## 2 6.59e7 jaimeberenguer "Digo lo que pi… 2009-08-15 00:00:00 49753
## 3 6.83e7 yiruim "Artista del ex… 2009-08-24 00:00:00 1669
## 4 6.97e7 carvar83 "Marketero de c… 2009-08-28 00:00:00 127
## 5 8.04e7 Cheap_Movies "Tengo las neur… 2009-10-06 00:00:00 584
## 6 8.58e7 curra01 "vox chanmartin… 2009-10-28 00:00:00 4764
## 7 9.27e7 MiguelPascual "Secretario pro… 2009-11-26 00:00:00 1690
## 8 9.43e7 cristinapelaez "Concejal Porta… 2009-12-03 00:00:00 8578
## 9 1.09e8 pitapulgarcit4 "\"Al final los… 2010-01-27 00:00:00 15490
## 10 1.21e8 aresauria "IQI 👩🏽🔬 https:/… 2010-03-09 00:00:00 258
## # ℹ 359 more rows
## # ℹ 1 more variable: user_friends <int>
Sacamos los tweets de los usuarios en contra de la ley.
df_tweets_vox <- df_filtered %>%
inner_join(df_usuarios_vox, by = "user_id")
Como hemos visto antes, las feministas TERFs son las que mayor oposición muestran a la ley. Ya hemos comentado antes que el termino TERF se usa de forma despectiva, hemos observado que las feministas terfs suelen denominarse así mismas como “radfem” (abreviatura de radical feminist), así que sacaremos a las usuarias que tengan en su descripción la palabra “radfem”.
df_usuarios_terf <- df_usuarios %>%
filter(str_detect(user_description, "radfem"))
df_usuarios_terf
## # A tibble: 204 × 6
## user_id user_screen_name user_description user_created_at_iso user_followers
## <dbl> <chr> <chr> <dttm> <int>
## 1 2.09e7 Thornqueen "professional l… 2009-02-15 00:00:00 49
## 2 5.01e7 Sandri_IC "ficciones.\nra… 2009-06-23 00:00:00 231
## 3 6.49e7 blutgirl "hembra/humana/… 2009-08-12 00:00:00 2585
## 4 9.19e7 IbtissameBetty "Clinical Psych… 2009-11-22 00:00:00 11068
## 5 1.07e8 zorra_basica "radfem.\nvegan… 2010-01-22 00:00:00 376
## 6 1.14e8 MayGaal "No entres a mi… 2010-02-15 00:00:00 535
## 7 1.19e8 CarmenStonem "Antiespecista,… 2010-03-02 00:00:00 184
## 8 1.33e8 Lady_Of_Cydonia "#radfem #teach… 2010-04-14 00:00:00 604
## 9 1.48e8 russiandoll__ "⚢ \n\n.\n\n\n♀… 2010-05-24 00:00:00 406
## 10 1.86e8 valeriapinedam "learning radfe… 2010-09-02 00:00:00 536
## # ℹ 194 more rows
## # ℹ 1 more variable: user_friends <int>
Sacamos los tweets de las usuarias TERFs.
df_tweets_terf <- df_filtered %>%
inner_join(df_usuarios_terf, by = "user_id")
Para ver si efectivamente hemos agrupado a los usuarios correctamente, vamos a sacar las palabras más frecuentes en cada comunidad.
Primero, sacamos las palabras más frecuentes en la comunidad a favor de la ley.
# Creamos un corpus con los tweets
corpus_tweets_favor <- corpus(df_tweets_favor$tweet_full_text)
# Generamos los tokens
token_tweets_favor <-quanteda::tokens(corpus_tweets_favor,
what = "word",
remove_numbers = TRUE,
remove_punct = TRUE,
remove_symbols = TRUE,
remove_separators = TRUE,
remove_url = TRUE)
Creamos la matriz de frecuencia de las palabras.
# Generamos los bigramas
bigramas_favor <- tokens_ngrams(token_tweets_favor, n = 2)
# Generamos la matriz de frecuencia de los tweets
matrix_tweets_favor <-dfm(bigramas_favor)
# Vemos las 10 palabras más frecuentes
top_10 <- topfeatures(matrix_tweets_favor, 10)
top_10
## identidad_género derechos_personas derechos_humanos
## 206 141 95
## carmen_calvo última_hora autodeterminación_género
## 85 80 76
## huelga_hambre personas_binarias derechos_lgtbi
## 75 68 61
## hoy_día
## 61
En la comunidad a favor de la ley aparecen bigramas como “identidad_género”, “derechos_personas”, “derechos_humanos” y “derechos_lgtbi” que reflejan una opinión positiva sobre la ley.
Ahora sacamos las palabras más frecuentes en la comunidad de usuarios de vox.
# Creamos un corpus con los tweets
corpus_tweets_vox <- corpus(df_tweets_vox$tweet_full_text)
# Generamos los tokens
token_tweets_vox <-quanteda::tokens(corpus_tweets_vox,
what = "word",
remove_numbers = TRUE,
remove_punct = TRUE,
remove_symbols = TRUE,
remove_separators = TRUE,
remove_url = TRUE)
# Generamos los bigramas
bigramas_vox <- tokens_ngrams(token_tweets_vox, n = 2)
# Generamos la matriz de frecuencia de los tweets
matrix_tweets_vox <-dfm(bigramas_vox)
# Generamos el wordcloud
top_10_vox <- topfeatures(matrix_tweets_vox, 10)
top_10_vox
## irene_montero amenaza_mujeres mujeres_niños santiabascal_semana
## 130 106 101 53
## semana_dosis dosis_atropello atropello_derechos derechos_distopía
## 53 53 53 53
## distopía_sanchista sanchista_indultos
## 53 53
En la comunidad de los usuarios de vox aparecen bigramas como “amenaza_mujeres”, “derechos_distopía”, “distopía_sanchista” que reflejan una opinión negativa sobre la ley.
Por último, sacamos las palabras más frecuentes en la comunidad de usuarias TERFs.
# Creamos un corpus con los tweets
corpus_tweets_terf <- corpus(df_tweets_terf$tweet_full_text)
# Generamos los tokens
token_tweets_terf <-quanteda::tokens(corpus_tweets_terf,
what = "word",
remove_numbers = TRUE,
remove_punct = TRUE,
remove_symbols = TRUE,
remove_separators = TRUE,
remove_url = TRUE)
# Generamos los bigramas
bigramas_terf <- tokens_ngrams(token_tweets_terf, n = 2)
# Generamos la matriz de frecuencia de los tweets
matrix_tweets_terf <-dfm(bigramas_terf)
# Generamos el top 10
top_10_terf <- topfeatures(matrix_tweets_terf, 10)
top_10_terf
## mal_llamada irene_montero derechos_mujeres
## 150 93 81
## personas_transexuales vía_urgencia ser_mujer
## 57 50 46
## cada_vez violencia_género consejo_ministros
## 45 45 43
## movimiento_feminista
## 41
La frase “mal llamada”, se usa en muchos tweets en los que se critica la ley (“la mal llamada ley trans…”), lo mismo que “ser mujer” que son tweets en los que dicen “Quiero ser mujer, es mi derecho como hombre”. Por lo tanto se ve que las usuarias TERFs, además de hablar del feminismo y de la violencia de género, también están en contra de la ley.
Generaremos un grafo en el que los nodos verdes representarán a los usuarios en contra y los nodos morados a los usuarios a favor.
Primero, de los tweets de cada comunidad, seleccionaremos solo los tweets cuyos autores y destinatarios pertenezcan a alguna de las comunidades.
df_tweets_comunidades <- rbind(df_tweets_favor, df_tweets_vox, df_tweets_terf)
Creamos un data frame con las columnas user_id y
in_reply_to_user_id de las comunidades.
df_interacciones <- df_tweets_comunidades %>%
select(user_id, in_reply_to_user_id) %>%
na.omit()
Creamos un grafo general con todas las interacciones. Los nodos tendrán un color distinto en función de la comunidad a la que pertenezcan. Si no pertenecen a ninguna comunidad se les asignará un color gris, si pertenecen a la comunidad a favor se les asignará un color rojo, si pertenecen a la comunidad de usuarios de vox se le asignará un color verde y si pertenecen a la comunidad de usuarias TERFs se les asignará un color morado.
grafo_comunidades <- graph_from_data_frame(df_interacciones, directed = TRUE)
# Asignamos un color a cada nodo
V(grafo_comunidades)$color <- ifelse(V(grafo_comunidades)$name %in% df_usuarios_favor$user_id, "red",
ifelse(V(grafo_comunidades)$name %in% df_usuarios_vox$user_id, "green",
ifelse(V(grafo_comunidades)$name %in% df_usuarios_terf$user_id, "purple", "gray")))
Por último, mostramos el grafo.
plot(
grafo_comunidades,
vertex.color = V(grafo_comunidades)$color,
vertex.size = 5,
edge.arrow.size = 0.2,
edge.color = "gray",
vertex.label = NA,
main = "Grafo de interacciones de las comunidades"
)
legend("bottomright", legend = c("A favor", "Vox", "TERF", "Neutro"), fill = c("red", "green", "purple", "gray"))
Observamos que las comunidades están bastante dispersas, esto se puede deber a que en twitter predomina el “debate” y la interacción entre usuarios de distintas opiniones. Además, al ser un tema tan polarizado, es probable que muchos usuarios interactúen con usuarios de la opinión contraria.
También encontramos nodos morados y rojos que atraen a muchos nodos neutros, lo que puede indicar que las usuarias TERFs y los usuarios a favor de la ley tienen una gran influencia en la red.
Este proyecto ha cambiado nuestra percepción sobre el fenómeno de la ley trans en España. Incialmente pensábamos que predominarían los sentimientos negativos ya que se trata de un tema muy polémico, además de que la fama de twitter es la de ser una red social en la que se comparten opiniones negativas. Sin embargo, hemos visto que los sentimientos positivos y negativos están bastante equilibrados, ganando los positivos, debido a que en el dataset analizado la ley tiene más apoyo que detractores.
Otro aspecto que nos ha sorprendido es que la mayoría de detractores de la ley son feministas TERF, y no votantes de derechas como pensábamos en un principio. Algunos de los tweets que hemos visto de las usuarias TERFs son muy agresivos y radicales, lo cual no nos esperábamos de usuarios que comparten espectro político con las personas a favor de la ley.
En general, hemos comprobado de que se trata de un tema muy pasional y polarizado, que aunque en general hay más apoyo que detractores, ambas partes tienen sus ideas muy claras y no están dispuestas a ceder.